/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/app/debug_console.hpp>
#include <baja/common.hpp>
#include <baja/trace.hpp>
#include <time.h>

namespace baja { namespace app {

static const baja::wchar* c_traceLogFileName = L"trace_log.txt";

#define TRACE_TAG_OTHER 0xf0000000
// special tag to always show these traces no matter what
#define TRACE_TAG_ALWAYS 0x1000000

#define TRACE_FLAG_FILE_TRACING	0x1
#define TRACE_FLAG_OUTPUT_TRACING 0x2

template <class Bogus> struct enabled_tags
{
    static uint64 tags;
    static uint64 flags;
    static HANDLE traceFileHandle;
    static std::wstring traceFilePath;
};

uint64 enabled_tags<int>::tags = 0x0;
uint64 enabled_tags<int>::flags = 0x0;
HANDLE enabled_tags<int>::traceFileHandle = INVALID_HANDLE_VALUE;
std::wstring enabled_tags<int>::traceFilePath;

inline void debugConsoleEnableTag(const uint64 tag)
{
    enabled_tags<int>::tags |= tag;
}

inline void debugConsoleDisableTag(const uint64 tag)
{
    enabled_tags<int>::tags &= ~tag;
}

inline void debugConsoleSetFlag(const uint64 flag)
{
    enabled_tags<int>::flags |= flag;
}

inline void debugConsoleClearFlag(const uint64 flag)
{
    enabled_tags<int>::flags &= ~flag;
}

inline bool debugConsoleIsTagEnabled(const uint64 tag)
{
    return (tag == TRACE_TAG_ALWAYS || (tag & enabled_tags<int>::tags) != 0);
}

inline std::wstring generateLocalFullFileName(const std::wstring& fileName)
{
    std::wstring targetFolder(Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data());

    std::wostringstream outs;
    outs << targetFolder;
    outs << L"\\";
    outs << fileName;

    return std::wstring(outs.str());
}

inline void debugConsoleTraceToFile(const wchar* msg)
{
    if (enabled_tags<int>::traceFilePath.length() == 0)
    {
        enabled_tags<int>::traceFilePath = generateLocalFullFileName(c_traceLogFileName);
    }

    if (enabled_tags<int>::traceFileHandle == INVALID_HANDLE_VALUE)
    {
        enabled_tags<int>::traceFileHandle = ::CreateFile2(
            enabled_tags<int>::traceFilePath.c_str(),
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ,
            CREATE_ALWAYS,
            nullptr
            );
    }

    if (enabled_tags<int>::traceFileHandle == INVALID_HANDLE_VALUE)
    {
        // just ignore and quit trying if we couldn't open it for some reason
        return;
    }

    DWORD written = 0;

    struct tm *current;
    time_t now;
    time(&now);
    current = localtime(&now);

    boost::wformat fmt = boost::wformat(L"%i:%i:%i: %s\r\n") % 
        current->tm_hour %
        current->tm_min %
        current->tm_sec %
        (msg ? msg : L"<null>");

    std::wstring ws = fmt.str();

    ::WriteFile(
        enabled_tags<int>::traceFileHandle,
        (LPCVOID)ws.data(),
        static_cast<DWORD>(ws.length() * sizeof(wchar)),
        &written,
        nullptr
        );
}

template<class Tmsg> void debugConsoleTrace(const Tmsg& msg, const uint64 tag = TRACE_TAG_OTHER, const bool undecorated = false)
{
    // we use a string stream so we can leverage the << operator which should already be 
    // wired up to take in strings, boost::format, char* and any other types we care about 
    std::wostringstream outs;
    if (!undecorated)
    {
        outs << boost::wformat(L"%06x") % GetCurrentThreadId() << ": ";
    }
    outs << msg;
    std::wstring str = outs.str();

    std::shared_ptr<idebug_console> debugConsole = baja::services::get<idebug_console>();
    if (!debugConsole) return;

    debugConsole->trace(str.c_str());

    if (enabled_tags<int>::flags & TRACE_FLAG_OUTPUT_TRACING)
    {
        baja::bajaTrace(wstrtostr(str.c_str()), undecorated);
    }

    if (enabled_tags<int>::flags & TRACE_FLAG_FILE_TRACING)
    {
        debugConsoleTraceToFile(str.c_str());
    }
}

#define DEBUG_CONSOLE_IS_TAG_ENABLED(tag) baja::app::debugConsoleIsTagEnabled(tag)
#define DEBUG_CONSOLE_TRACE_TO_FILE(msg) baja::app::debugConsoleTraceToFile(msg);
#define DEBUG_CONSOLE_TRACE(msg) if (baja::app::debugConsoleIsTagEnabled(TRACE_TAG_OTHER)) { baja::app::debugConsoleTrace(msg); }
#define DEBUG_CONSOLE_TRACE_TAG(tag, msg) if (baja::app::debugConsoleIsTagEnabled(tag)) { baja::app::debugConsoleTrace(msg, tag); }
#define DEBUG_CONSOLE_ENABLE_TAG(tag) baja::app::debugConsoleEnableTag(tag);
#define DEBUG_CONSOLE_DISABLE_TAG(tag) baja::app::debugConsoleDisableTag(tag);
#define DEBUG_CONSOLE_SET_FLAG(flag) baja::app::debugConsoleSetFlag(flag);
#define DEBUG_CONSOLE_CLEAR_FLAG(flag) baja::app::debugConsoleClearFlag(flag);

}} // namespace baja::app